<!-- src/components/LifeLines.vue -->
<template>
    <div>
        <div style="display: flex; gap: 12px; align-items: center; margin-bottom: 8px">
            <!-- <el-select
                v-model="sourceLocal"
                placeholder="Source"
                size="small"
                @change="onSourceChange"
                class="ctrl short-select"
            >
                <el-option label="RNA" value="rna" />
                <el-option label="WES" value="wes" />
            </el-select> -->

            <el-select
                v-model="selectedSource"
                placeholder="选择数据源"
                size="small"
                filterable
                style="min-width: 150px; max-width: 200px;"
                @change="onSourceChange"
                >
                <el-option
                    v-for="s in sources"
                    :key="s.id"
                    :label="`${s.label} (${s.type})`"
                    :value="s.value"
                />
            </el-select>

            <el-input
                v-model="geneLocal"
                placeholder="Gene (e.g. TP53)"
                size="small"
                @keyup.enter="refresh"
                class="ctrl grow-input"
            />

            <el-button type="primary" size="small" @click="refresh">刷新</el-button>

            <el-button
                v-if="downloadLinks?.selected_csv"
                type="text"
                @click="download(downloadLinks.selected_csv, 'selected.csv')"
                target="_blank"
            >
                下载 Selected CSV
            </el-button>
            <el-button
                v-if="downloadLinks?.remaining_csv"
                type="text"
                @click="download(downloadLinks.remaining_csv, 'remaining.csv')"
                target="_blank"
            >
                下载 Remaining CSV
            </el-button>
        </div>

        <!-- 图表容器，这个元素会作为 loading 的 target（局部 loading） -->
        <div ref="chartRef" style="height: 480px; width: 100%" />
    </div>
</template>

<script setup lang="ts" name="LifeLines">
    import { ref, watch, onMounted, onBeforeUnmount } from 'vue';
    import * as echarts from 'echarts';
    import { post, get } from '@/axios/api'; // 你的 axios wrapper
    import { ElMessage, ElLoading } from 'element-plus';
    import { download } from '@/common/util';
    import { isEqual } from 'lodash';

    const props = defineProps({
        selectedSampleIDs: { type: Array, default: () => [] },
        gene: { type: String, default: 'TP53' },
        source: { type: String, default: 'wes' },
        group: { type: String, default: 'median' }
    });
    const emit = defineEmits(['ready']);

    const chartRef = ref(null);
    let chart:any = null;

    const geneLocal = ref(props.gene);
    const sourceLocal = ref(props.source);
    const data:any = ref(null);
    const summary = ref(null);
    const downloadLinks:any = ref(null);

    const sources = ref<any[]>([
        { label: 'aitl.250829v2.csv', value: 'aitl.250829v2.csv', type: 'wes' },
        { label: 'wes.250815.csv', value: 'wes.250815.csv', type: 'wes' },
        { label: 'RNAseq结果.csv', value: 'RNAseq结果.csv', type: 'rna' },
    ]);
    const selectedSource = ref<string | null>("aitl.250829v2.csv");

    // loading instance (ElLoading) 仅在组件内部使用
    let loadingInstance:any = null;

    // 竞态保护：只接受最新一次请求的返回
    let latestRequestId = 0;

    function survivalAt(timeline:any, survival:any, t:any) {
        if (!Array.isArray(timeline) || !Array.isArray(survival) || timeline.length === 0) return 1.0;
        let last = survival[0];
        for (let i = 0; i < timeline.length; i++) {
            if (t < timeline[i]) break;
            last = survival[i];
        }
        return last;
    }

    async function fetchKM() {
        // validation for RNA
        if (!geneLocal.value && sourceLocal.value === 'rna') {
            ElMessage.warning('请先输入 gene 名称（RNA source 需要 gene）');
            return;
        }

        // increase request id and capture local id
        latestRequestId += 1;
        const thisReqId = latestRequestId;

        // local snapshot of selected ids at request start to avoid mid-flight prop changes ambiguity
        const selectedSnapshot = Array.isArray(props.selectedSampleIDs) ? props.selectedSampleIDs.slice() : [];

        // show local loading overlay targeted to chartRef element (so only this component shows it)
        try {
            if (chartRef.value) {
                loadingInstance = ElLoading.service({
                    target: chartRef.value,
                    text: '绘制生存曲线中...',
                    lock: true,
                    background: 'rgba(255,255,255,0.7)'
                });
            }

            let res:any = null;
            if (selectedSnapshot && selectedSnapshot.length > 0) {
                // POST: ask backend for Selected + Remaining
                res = await post('/chart/lifeline/', {
                    source: sourceLocal.value,
                    gene: geneLocal.value,
                    group: props.group,
                    selected_ids: selectedSnapshot,
                    path: selectedSource.value,
                });
            } else {
                // GET: overall KM
                res = await get('/chart/lifeline/', {
                    source: sourceLocal.value,
                    gene: geneLocal.value,
                    group: props.group,
                    path: selectedSource.value,
                });
            }

            // support axios wrapper returning { data: ... } or raw object
            if (res && res.data) res = res.data;

            if (!res || typeof res !== 'object') throw new Error('Invalid response from server');

            // 如果这不是最新请求（已有更晚的请求启动），则忽略此返回（避免覆盖）
            if (thisReqId !== latestRequestId) {
                // 忽略旧的响应
                return;
            }

            // 应用返回值到组件状态并绘图
            data.value = res;
            summary.value = res.summary || null;
            downloadLinks.value = res.downloads || null;

            renderChart();
            emit('ready', res);
        } catch (err:any) {
            console.error('fetchKM error', err);
            const msg:any = err?.response?.data?.detail ?? err.message ?? String(err);
            ElMessage.error('生存曲线获取失败: ' + msg);
        } finally {
            // 仅关闭当前 loading 实例（如果存在）
            if (loadingInstance && typeof loadingInstance.close === 'function') {
                loadingInstance.close();
                loadingInstance = null;
            }
        }
    }

    function buildSeriesFromResp(resp:any) {
        const series:any = [];
        const scatterSeries:any = [];

        if (!resp || typeof resp !== 'object') return { series, scatterSeries };
        const groups:any = resp.groups;
        if (!groups || typeof groups !== 'object') return { series, scatterSeries };

        Object.entries(groups).forEach(([grp, gdata]: [string, any]) => {
            if (!gdata || !Array.isArray(gdata.timeline) || !Array.isArray(gdata.survival)) {
                // 无效组（例如样本不足），跳过但不抛错
                return;
            }

            // 线条 points
            const points:any = [];
            for (let i = 0; i < gdata.timeline.length; i++) {
                const tx:any = parseFloat(gdata.timeline[i]);
                const sy:any = parseFloat(gdata.survival[i]);
                if (!Number.isFinite(tx) || !Number.isFinite(sy)) continue;
                points.push([tx, sy]);
            }
            series.push({
                name: grp,
                type: 'line',
                step: 'end',
                data: points,
                showSymbol: false,
                lineStyle: { width: 2 }
            });

            // events / censored
            const evList:any = Array.isArray(gdata.events) ? gdata.events : [];
            const censList:any = Array.isArray(gdata.censored) ? gdata.censored : [];

            const evPts = evList
                .map((ev: any) => {
                    const timeRaw = ev.time ?? ev['OS.time'] ?? ev.OS_time ?? ev.Time;
                    const sampleId = ev.SampleID ?? ev.sample ?? ev.SampleId ?? ev.ID;
                    const t = parseFloat(timeRaw);
                    if (!Number.isFinite(t)) return null;
                    const y = survivalAt(gdata.timeline, gdata.survival, t);
                    return { value: [t, y], patient: sampleId, time: t, group: grp };
                })
                .filter(Boolean);

            const censPts = censList
                .map((ev: any) => {
                    const timeRaw = ev.time ?? ev['OS.time'] ?? ev.OS_time ?? ev.Time;
                    const sampleId = ev.SampleID ?? ev.sample ?? ev.SampleId ?? ev.ID;
                    const t = parseFloat(timeRaw);
                    if (!Number.isFinite(t)) return null;
                    const y = survivalAt(gdata.timeline, gdata.survival, t);
                    return { value: [t, y], patient: sampleId, time: t, group: grp };
                })
                .filter(Boolean);

            if (evPts.length > 0) {
                scatterSeries.push({
                    name: `${grp} events`,
                    type: 'scatter',
                    data: evPts,
                    symbol: 'circle',
                    symbolSize: 8,
                    itemStyle: { borderColor: '#000', borderWidth: 1 }
                });
            }
            if (censPts.length > 0) {
                scatterSeries.push({
                    name: `${grp} censored`,
                    type: 'scatter',
                    data: censPts,
                    symbol: 'triangle',
                    symbolSize: 6,
                    itemStyle: { opacity: 0.9 }
                });
            }
        });

        return { series, scatterSeries };
    }

    function renderChart() {
        if (!chartRef.value) return;
        if (!chart) {
            chart = echarts.init(chartRef.value);
            window.addEventListener('resize', () => chart && chart.resize());
        }

        const { series, scatterSeries } = buildSeriesFromResp(data.value || {});
        if (series.length + scatterSeries.length === 0) {
            chart.clear();
            chart.setOption({
                title: { text: `Survival Curve — ${geneLocal.value || ''}` },
                graphic: [
                    {
                        type: 'text',
                        left: 'center',
                        top: '45%',
                        style: { text: 'No valid groups/points to display', fontSize: 14, fill: '#999' }
                    }
                ]
            });
            return;
        }

        const option = {
            title: { text: `Survival Curve — ${geneLocal.value || ''}`, top: 0 },
            tooltip: {
                trigger: 'item',
                confine: true,
                formatter: function (params:any) {
                    if (params.seriesType === 'line') {
                        return `${params.seriesName}<br/>Time: ${
                            params.value[0]
                        }<br/>Survival: ${params.value[1].toFixed(3)}`;
                    } else if (params.seriesType === 'scatter') {
                        const p = params.data;
                        return `${params.seriesName}<br/>Patient: ${p.patient || '-'}<br/>Time: ${
                            p.time
                        }<br/>Survival: ${p.value[1].toFixed(3)}`;
                    }
                    return params.name;
                }
            },
            xAxis: { name: 'Time', type: 'value' },
            yAxis: { name: 'Survival Probability', min: 0, max: 1, type: 'value' },
            legend: { top: 30 },
            grid: { right: 20, left: 60, bottom: 60 },
            series: [...series, ...scatterSeries]
        };

        chart.setOption(option, true);
    }

    function refresh() {
        fetchKM();
    }

    // in case user changes source from dropdown
    function onSourceChange(val:any) {
        const obj = sources.value.find(s => s.value === val)
        sourceLocal.value = obj.type
        // update local state already via v-model, then refresh chart
        fetchKM();
    }

    onMounted(() => {
        // 初始渲染：会使用 props.selectedSampleIDs（若为空则做 GET）
        fetchKM();
    });

    onBeforeUnmount(() => {
        if (chart) {
            chart.dispose();
            chart = null;
        }
    });

    // 监听传进来的 selectedSampleIDs、gene、source（debounce not needed because requestId 会保护）
    watch(
        () => props.selectedSampleIDs,
        (newIDs, oldIDs) => {
            // 使用lodash的isEqual进行深层比较
            if (!isEqual(newIDs, oldIDs)) {
                console.log(props.selectedSampleIDs);
                fetchKM();
            }
        },
        { deep: true }
    );
    watch(
        () => props.gene,
        nv => {
            geneLocal.value = nv;
            fetchKM();
        }
    );
    watch(
        () => props.source,
        nv => {
            sourceLocal.value = nv;
            fetchKM();
        }
    );
</script>
<style scoped>
    .search-controls {
        display: flex;
        gap: 12px;
        align-items: center;
        margin-bottom: 8px;
        width: 100%;
    }

    /* 基本控件共用 */
    .ctrl {
        box-sizing: border-box;
    }

    /* 窄的 select（固定宽） */
    .short-select {
        flex: 0 0 110px; /* 不放大不缩小，固定 110px */
        min-width: 90px;
        max-width: 140px;
    }

    /* gene 输入：占据剩余空间 */
    .grow-input {
        flex: 1 1 220px; /* 可伸缩，基础宽 220px */
        min-width: 160px;
        max-width: 520px;
    }

    /* 下载按钮区域靠右对齐 */
    .download-actions {
        margin-left: auto;
        display: flex;
        gap: 8px;
        align-items: center;
    }

    /* 响应式：窄屏下堆叠控件（可选） */
    @media (max-width: 720px) {
        .search-controls {
            flex-wrap: wrap;
            gap: 8px;
        }
        .short-select,
        .grow-input {
            flex: 1 1 100%;
            min-width: 0;
            max-width: 100%;
        }
        .download-actions {
            width: 100%;
            justify-content: flex-start;
            margin-left: 0;
        }
    }
</style>
